package com.qfmy.service.impl.novel;


import com.alibaba.cloud.nacos.utils.StringUtils;
import com.qfmy.lucene.LuceneUtils;
import com.qfmy.mapper.novel.HistoryMapper;
import com.qfmy.mapper.novel.NovelMapper;
import com.qfmy.mapper.user.UserInfoMapper;
import com.qfmy.service.novel.NovelService;
import com.qfmy.vo.novel.NovelVo;
import common.exception.BaseException;
import common.login.LoginUser;
import common.login.LoginUserHolder;
import common.result.ResultCodeEnum;
import enmu.NovelStatus;
import lombok.extern.slf4j.Slf4j;
import model.annotation.BreezeRedisCacheAnnotation;
import model.entity.novel.Novel;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;


import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;


/**
 * @author 清风明月
 * @Package com.qfmy.service.impl.novel
 * @date 2025/6/6
 * @description 小说
 */
@Slf4j
@Service
@SuppressWarnings("all")
public  class NovelServiceImpl implements NovelService {

    /**
     * 注入mapper
     */
    @Autowired
    private NovelMapper novelMapper;

    /**
     * 注入RedisTemplate
     */
    @Autowired
    private RedisTemplate<String,Object> redisTemplate;

    /**
     * 注入redission
     */
    @Autowired
    private RedissonClient redissonClient;

    /**
     * 注入用户的历史
     */
    @Autowired
    private HistoryMapper historyMapper;

    /**
     * 注入用户mapper
     */
    @Autowired
    private UserInfoMapper userInfoMapper;

    /**
     * 随机数
     */
    private Random random = new Random();

//    /**
//     * 获取热门小说
//     * @return
//     */
//    @Override
//    public List<NovelVo> getHotNovels() {
//        //从缓存里面获取
//        List<NovelVo> hotNovels = (List<NovelVo>) redisTemplate.opsForValue().get("hotNovels");
//        //判断是否存在
//        if (hotNovels != null) {
//            return hotNovels;
//        }
//        //使用双检查的方式防止缓存穿透
//        synchronized (this)
//        {
//            //再次判断缓存是否为空
//            hotNovels = (List<NovelVo>) redisTemplate.opsForValue().get("hotNovels");
//            if(hotNovels != null)
//            {
//                return hotNovels;
//            }
//            //查询数据
//            List<Novel> novels = novelMapper.getHotNovels();
//            //判断是否为空 ->使用空对象的方式实现缓存穿透
//            if (novels.size() == 0 || novels.isEmpty())
//            {
//                //设置空数据
//                hotNovels = new ArrayList<>();
//                redisTemplate.opsForValue().set("hotNovels", hotNovels, 5,TimeUnit.MINUTES);
//                return Collections.emptyList();
//            }
//            //拷贝数据
//            hotNovels = Copy(novels);
//            List<NovelVo> finalHotNovels = hotNovels;
//            //设置缓存过期时间
//            //随机缓存时间
//            int randomMinutes = 120 + random.nextInt(61); // 生成120到180之间的随机分钟数
//            redisTemplate.opsForValue().set("hotNovels", finalHotNovels, randomMinutes, TimeUnit.MINUTES);
//        }
//        return hotNovels;
//    }

    /**
     * 获取热门小说
     * @return
     */
    @Override
    @BreezeRedisCacheAnnotation(KeyPrefix = "hotNovels:", expire = 2, timeUnit = TimeUnit.HOURS, isCacheNull = true)
    public List<NovelVo> getHotNovels() {
        //查询数据
        List<Novel> novels = novelMapper.getHotNovels();
        return Copy(novels);
    }

//    /**
//     * 根据分类获取热门小说
//     * @param name
//     * @return
//     */
//    @Override
//    public List<NovelVo> getHotNovelsByType(String name) {
//        //创建缓存键
//        String key = "hotNovels:" + name;
//        List<NovelVo> hotNovels = (List<NovelVo>) redisTemplate.opsForValue().get(key);
//        if (hotNovels != null) {
//            return hotNovels;
//        }
//        //使用双检查的方式防止缓存穿透
//        synchronized (this)
//        {
//            //再次从缓存里面获取数据
//            hotNovels = (List<NovelVo>) redisTemplate.opsForValue().get(key);
//            if (hotNovels != null) {
//                return hotNovels;
//            }
//            //从数据库里面获取数据
//            List<Novel> novels = novelMapper.getHotNovelsByType(name);
//            //判断是否为空 ->使用空对象的方式实现缓存穿透
//            if (novels.size() == 0 || novels.isEmpty())
//            {
//                //设置空数据
//                hotNovels = new ArrayList<>();
//                redisTemplate.opsForValue().set(key, hotNovels, 5,TimeUnit.MINUTES);
//                return Collections.emptyList();
//            }
//            //拷贝数据
//            hotNovels = Copy(novels);
//            // 设置随机过期时间，避免缓存雪崩
//            long expirationTime = 7200 + ThreadLocalRandom.current().nextInt(3600); // 2-3小时随机
//            redisTemplate.opsForValue().set(key, hotNovels, expirationTime, TimeUnit.SECONDS);
//        }
//        return hotNovels;
//    }
    /**
     * 根据分类获取热门小说
     * @param name
     * @return
     */
    @Override
    @BreezeRedisCacheAnnotation(KeyPrefix = "hotNovels:", expire = 1, timeUnit = TimeUnit.HOURS, isCacheNull = true,placeholder = "#name")
    public List<NovelVo> getHotNovelsByType(String name) {
        //从数据库里面获取数据
        List<Novel> novels = novelMapper.getHotNovelsByType(name);
        return Copy(novels);
    }

    /**
     * 获取排行榜
     * @return
     */
    @Override
    public List<NovelVo> getRankNovels() {
        //创建缓存key
        String key = "rankNovels";
        List<NovelVo> rankNovels = (List<NovelVo>) redisTemplate.opsForValue().get(key);
        if (rankNovels != null) {
            return rankNovels;
        }
        //使用双检查的方式防止缓存穿透
        synchronized (this)
        {
            //再次获取数据
            rankNovels = (List<NovelVo>) redisTemplate.opsForValue().get(key);
            if(rankNovels != null)
            {
                return rankNovels;
            }
            //从数据库里面获取数据
            List<Novel> novels = novelMapper.getRankNovels();
            if (novels.size() == 0 || novels.isEmpty())
            {
                //设置空数据
                rankNovels = new ArrayList<>();
                redisTemplate.opsForValue().set(key, rankNovels, 5,TimeUnit.MINUTES);
                return Collections.emptyList();
            }
            rankNovels = Copy(novels);
            // 设置随机过期时间，避免缓存雪崩
            long expirationTime = 7200 + ThreadLocalRandom.current().nextInt(3600); // 2-3小时随机
            redisTemplate.opsForValue().set(key, rankNovels,expirationTime, TimeUnit.HOURS);
        }
        return rankNovels;
    }

    /**
     * 获取限时间免费小说
     * @return
     */
    @Override
    public List<NovelVo> getFreeNovels() {
        //创建缓存key
        String key = "freeNovels";
        List<NovelVo> freeNovels = (List<NovelVo>) redisTemplate.opsForValue().get(key);
        if (freeNovels != null) {
            return freeNovels;
        }
        //使用双检查的方式防止缓存穿透
        synchronized (this)
        {
            //再次获取数据
            freeNovels = (List<NovelVo>) redisTemplate.opsForValue().get(key);
            if(freeNovels != null)
            {
                return freeNovels;
            }
            //从数据库里面获取数据
            List<Novel> novels = novelMapper.getFreeNovels();
            if (novels.size() == 0 || novels.isEmpty())
            {
                //设置空数据
                freeNovels = new ArrayList<>();
                redisTemplate.opsForValue().set(key, freeNovels, 5,TimeUnit.MINUTES);
                return Collections.emptyList();
            }
            freeNovels = Copy(novels);
            // 设置随机过期时间，避免缓存雪崩
            long expirationTime = 7200 + ThreadLocalRandom.current().nextInt(3600); // 2-3小时随机
            redisTemplate.opsForValue().set(key, freeNovels,expirationTime, TimeUnit.HOURS);
        }
        return freeNovels;
    }

    /**
     * 获取用户喜欢的小说（含个性化推荐逻辑）
     * @param pageNum 页码
     * @param size 每页数量
     * @return 推荐的小说列表
     */
    @Override
    public List<NovelVo> getUserLikeNovels(Integer pageNum, Integer size) {
        // 1. 获取当前用户ID
        Long userId = getUserId();
        if (userId == null) return Collections.emptyList();

        // 2. 获取用户明确喜欢的类型（从用户表或偏好表获取）
        String userPreferredType = userInfoMapper.getUserPreferredType(userId);
        if (StringUtils.isBlank(userPreferredType)) {
            // 用户未设置偏好时，返回默认推荐（如热门小说）
            return getHotNovels();
        }

        // 3. 获取用户浏览历史（小说ID列表）
        List<Long> userViewHistory = historyMapper.selectHistortByUserId(userId);
        //判断是否为空
        if (userViewHistory == null || userViewHistory.isEmpty()) {
            return getHotNovels();
        }

        // 4. 执行推荐逻辑
        return recommendByUserPreferences(userPreferredType, userViewHistory, pageNum, size);
    }

    /**
     * 获取最新小说
     * @return
     */
    @Override
    public List<NovelVo> getNewestNovels() {
        //创建缓存key
        String key = "newestNovels";
        List<NovelVo> newestNovels = (List<NovelVo>) redisTemplate.opsForValue().get(key);
        if (newestNovels != null) {
            return newestNovels;
        }
        //使用双检查的方式防止缓存穿透
        synchronized (this)
        {
            //再次获取数据
            newestNovels = (List<NovelVo>) redisTemplate.opsForValue().get(key);
            if(newestNovels != null)
            {
                return newestNovels;
            }
            //从数据库里面获取数据
            List<Novel> novels = novelMapper.getNewestNovels();
            if (novels.size() == 0 || novels.isEmpty())
            {
                //设置空数据
                newestNovels = new ArrayList<>();
                redisTemplate.opsForValue().set(key, newestNovels, 5,TimeUnit.MINUTES);
                return Collections.emptyList();
            }
            newestNovels = Copy(novels);
            redisTemplate.opsForValue().set(key, newestNovels,12, TimeUnit.HOURS);
        }
        return newestNovels;
    }

    /**
     * 分页获取排行榜数据
     *
     * @param tag
     * @param pageNum
     * @param size
     * @return
     */
    @Override
    public List<NovelVo> getRankNovelsByPage(String tag, Integer pageNum, Integer size) {
        //创建缓存key
        String key = "rankNovelsByPage: " + tag + "_" + pageNum + "_" + size;
        List<NovelVo> rankNovels = (List<NovelVo>) redisTemplate.opsForValue().get(key);
        if (rankNovels != null) {
            return rankNovels;
        }
        //计算分页
        Integer start = getStart(pageNum, size);
        // 缓存未命中，使用分布式锁防止缓存穿透
        return getRankNovelsByPageFromDbWithLock(key, tag, start, size);
    }

    /**
     * @param type
     * @param pageNum
     * @param size
     * @param sort
     * @return
     */
    @Override
    public List<NovelVo> getNovelsByType(String type, Integer pageNum, Integer size, Integer sort) {
        //创建缓存key
        String key = "novelsByType: " + type + "_" + pageNum + "_" + size + "_" + sort;
        List<NovelVo> novelVos = (List<NovelVo>) redisTemplate.opsForValue().get(key);
        if (novelVos != null) {
            return novelVos;
        }
        //计算分页
        Integer start = getStart(pageNum, size);
        //获取锁
        synchronized (this)
        {
            //再次查询缓存
            novelVos = (List<NovelVo>) redisTemplate.opsForValue().get(key);
            if(novelVos != null)
            {
                return novelVos;
            }
            List<Novel> novels = new ArrayList<>();
            //根据顺序进行旋转
            switch (sort)
            {
                case 0:
                    novels = novelMapper.getNovelByTypePage(type, start, size);
                    break;
                case 1:
                    novels = novelMapper.getNovelsByTypeRank(type, start, size);
                    break;
                case 2:
                    novels = novelMapper.getNovelsByTypeAndScore(type, start, size);
                    break;
                case 3:
                    novels = novelMapper.getNovelsByTypeAndUpdateTime(type, start, size);
            }
            //判断是否为空
            if(novels == null)
            {
                novelVos = new ArrayList<>();
                redisTemplate.opsForValue().set(key, novelVos,5, TimeUnit.MINUTES);
                return Collections.emptyList();
            }
            novelVos = Copy(novels);
            redisTemplate.opsForValue().set(key, novelVos, 12, TimeUnit.HOURS);
        }
        return novelVos;
    }

    /**
     * 获取综合最新小说
     * @return
     */
    @Override
    public List<NovelVo> getNewRankNovels() {
        //创建缓存key
        String key = "novel:rank:new";
        List<NovelVo> novelVos = new ArrayList<>();
        if (redisTemplate.hasKey(key)) {
             novelVos = (List<NovelVo>) redisTemplate.opsForValue().get(key);
            return novelVos;
        }
        //缓存中没有数据
        synchronized (this)
        {
            //再次检测是否有数据
            novelVos = (List<NovelVo>) redisTemplate.opsForValue().get(key);
            if (novelVos != null) {
                return novelVos;
            }
            //从数据库里面获取
            List<Novel> novels = novelMapper.getNewRankNovels();
            //判断是否为空
            if (novels == null || novels.isEmpty()) {
               novels = new ArrayList<>();
               redisTemplate.opsForValue().set(key, novels, 5, TimeUnit.MINUTES);
                return Collections.emptyList();
            }
            novelVos = Copy(novels);
            redisTemplate.opsForValue().set(key, novelVos, 12, TimeUnit.HOURS);
        }
        return novelVos;
    }

    /**
     * 获取 potentialNewBook
     * @return
     */
    @Override
    public List<NovelVo> getPotentialNewBook() {
        //创建缓存key
        String key = "potentialNewBook";
        List<NovelVo> novelVos = (List<NovelVo>) redisTemplate.opsForValue().get(key);
        if (novelVos != null) {
            return novelVos;
        }
        //创建锁的key
        String lockKey = "lock:potentialNewBook";
        RLock lock = redissonClient.getLock(lockKey);
        try {
            boolean isTrue = lock.tryLock(3, 30, TimeUnit.SECONDS);
            //判断是存在
            if(isTrue)
            {
                //再次检测缓存
                novelVos = (List<NovelVo>) redisTemplate.opsForValue().get(key);
                if (novelVos != null) {
                    return novelVos;
                }
                //查询数据库
                List<Novel> novels = novelMapper.getPotentialNewBook();
                //判断是否存在
                if(novels == null)
                {
                    novelVos = new ArrayList<>();
                    redisTemplate.opsForValue().set(key, novelVos,5, TimeUnit.MINUTES);
                    return Collections.emptyList();
                }
                //拷贝数据
                novelVos = Copy(novels);
                //随机缓存时间
                int randomMinutes = 1200 + random.nextInt(61); // 生成1200到1260之间的随机分钟数
                redisTemplate.opsForValue().set(key, novelVos, randomMinutes, TimeUnit.MINUTES);
            }
        } catch (InterruptedException e) {
            log.error("Error acquiring lock");
        }finally {
            //释放锁
            lock.unlock();
        }
        return novelVos;
    }

    /**
     * 获取新书首发
     * @return
     */
    @Override
    public List<NovelVo> getNewBookReleases() {
        //创建缓存key
        String key = "newBookReleases";
        List<NovelVo> novelVos = (List<NovelVo>) redisTemplate.opsForValue().get(key);
        if (novelVos != null) {
            return novelVos;
        }
        //从数据库里面查询数据
        List<Novel> novels = novelMapper.getNewBookReleases();
        //判断是否存在
        if (novels == null) {
            novels = new ArrayList<>();
            redisTemplate.opsForValue().set(key, novels, 4, TimeUnit.MINUTES);
            return Collections.emptyList();
        }
        //拷贝数据
        novelVos = Copy(novels);
        //存放到缓存里面
        redisTemplate.opsForValue().set(key, novels, 12, TimeUnit.HOURS);
        return novelVos;
    }

    /**
     * 获取所有的小说
     */
    @Override
    public void indexBooks() {
        List<Novel> novels = novelMapper.getAllNovel();
        try {
            LuceneUtils.createIndex(novels);
        } catch (Exception e) {
            log.error("创建索引失败");
            throw new RuntimeException(e);
        }
    }

    /**
     * 搜索小说
     * @param keyword
     * @return
     */
    @Override
    public List<NovelVo> searchBook(String keyword) {
        try {
            return LuceneUtils.searchBooks(keyword);
        } catch (Exception e) {
            log.error("搜索小说失败");
            throw new RuntimeException(e);
        }
    }

    /**
     * 获取热搜索数据
     * @return
     */
    @Override
    public List<NovelVo> getHotSearch(Integer pageNum, Integer size) {
        //计算分页
        Integer start = getStart(pageNum,size);
        //创建key
        String key = "hotSearch";
        //从缓存里面获取数据
        List<NovelVo> novelVos = (List<NovelVo>)redisTemplate.opsForValue().get(key);
        if ((novelVos != null && novelVos.size() > 0 ) && pageNum == 1) {
            return novelVos;
        }
        //查询数据库
        synchronized (this)
        {
            //再次查询缓存判断是否存在
            novelVos = (List<NovelVo>) redisTemplate.opsForValue().get(key);
            if ((novelVos != null && novelVos.size() > 0 ) && pageNum == 1) {
                return novelVos;
            }
            //查询数据库
            List<Novel> novels = novelMapper.getHotSearchNovel(start,size);
            if(novels == null)
            {
                novelVos = new ArrayList<>();
                redisTemplate.opsForValue().set(key, novelVos, 5,TimeUnit.MINUTES);
                return Collections.emptyList();
            }
            //拷贝数据
            novelVos = Copy(novels);
            //存入缓存
            // 设置随机过期时间，避免缓存雪崩
            long expirationTime = 7200 + ThreadLocalRandom.current().nextInt(3600); // 2-3小时随机
            redisTemplate.opsForValue().set(key, novelVos, expirationTime,TimeUnit.MINUTES);
        }
        return novelVos;
    }

    /**
     * 更新搜索次数
     * @param novelId
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateSearch(Long novelId) {
        //更新搜索次数
        novelMapper.updateSearch(novelId);
    }

    /**
     * 更新阅读次数
     * @param novelId
     */
    @Override
    public void updateViewCount(Long novelId) {
        novelMapper.updateViewCount(novelId);
    }

    /**
     * 获取小说详情
     * @param novelId
     * @return
     */
    @Override
    public NovelVo getNovelById(Long novelId) {
        //获取当前用户
        Long userId = getUserId();
        //获取小说详情
        Novel novel = novelMapper.getNovelById(novelId,  userId);
        //创建对象
        NovelVo novelVo = new NovelVo();
        //进行对象拷贝
        BeanUtils.copyProperties(novel, novelVo);
        novelVo.setStatus(NovelStatus.getStatusName(novel.getStatus()));
        novelVo.setChapters(novel.getChapters());
        novelVo.setShelf(novel.isShelf());
        return novelVo;
    }

    /**
     * 获取排行榜数据
     * @param key
     * @param tag
     * @param start
     * @param size
     * @return
     */
    @Nullable
    @Contract(pure = true)
    private List<NovelVo> getRankNovelsByPageFromDbWithLock(String key, String tag, Integer start, Integer size) {
        // 使用Redis分布式锁或本地锁防止缓存击穿
        RLock lock = redissonClient.getLock("lock:" + key);
        try {
            // 尝试获取锁，等待10秒，自动释放时间30秒
            if (lock.tryLock(10, 30, TimeUnit.SECONDS)) {
                // 再次检查缓存，避免锁竞争导致的重复查询
                List<NovelVo> cacheData = (List<NovelVo>) redisTemplate.opsForValue().get(key);
                if (cacheData != null) {
                    log.debug("二次检查缓存命中: {}", key);
                    return cacheData.isEmpty() ? Collections.emptyList() : cacheData;
                }

                // 从数据库获取数据
                List<Novel> novels = fetchNovelsFromDb(tag, start, size);

                // 转换数据并处理空值
                cacheData = Copy(novels);

                // 设置缓存，空值也需要缓存但时间较短
                redisTemplate.opsForValue().set(key, cacheData,
                        cacheData.isEmpty() ? 5 : 120, TimeUnit.MINUTES);

                log.info("从数据库加载并缓存数据: {}, 大小: {}", key, cacheData.size());
                return cacheData;
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.error("获取锁被中断", e);
        } catch (Exception e) {
            log.error("获取排行榜数据异常, key: {}", key, e);
        } finally {
            // 确保锁释放
            if (lock != null && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
        // 获取锁失败或发生异常时的降级策略
        return Collections.emptyList();
    }

    /**
     *
     * @param tag
     * @param start
     * @param size
     * @return
     */
    @Nullable
    @Contract(pure = true)
    private List<Novel> fetchNovelsFromDb(@NotNull String tag, Integer start, Integer size) {
        switch (tag) {
            case "recommend":
                return novelMapper.getRankNovelsByPage(start, size);
            case "complete":
                return novelMapper.getCompleteNovels(start, size);
            case "new":
                return novelMapper.getNewNovel(start, size);
            default:
                log.warn("未知的标签类型: {}", tag);
                return Collections.emptyList();
        }
    }

    /**
     * 基于用户偏好和浏览历史的推荐逻辑
     */
    @NotNull
    private List<NovelVo> recommendByUserPreferences(
            String preferredType,
            List<Long> viewHistory,
            Integer pageNum,
            Integer size
    ) {
        // 计算分页起始位置
        Integer start = getStart(pageNum, size);

        // 获取用户已喜欢/浏览的小说ID（去重）
        List<Long> excludedIds = new ArrayList<>();
        excludedIds.addAll(viewHistory);

        // 第一阶段：推荐用户偏好类型的小说（优先匹配）
        List<Novel> primaryRecommendations = novelMapper.findNovelsByType(
                preferredType,
                excludedIds,
                start,
                size
        );

        // 第二阶段：补充推荐浏览历史中的相关类型小说（当数量不足时）
        if (primaryRecommendations.size() < size && !viewHistory.isEmpty()) {
            // 提取浏览历史中的相关类型（排除偏好类型）
            List<String> relatedTypes = extractRelatedTypesFromHistory(viewHistory, preferredType);

            // 查询相关类型小说并补充
            List<Novel> supplementary = novelMapper.findByFeatures(
                    relatedTypes,
                    excludedIds,
                    0,
                    size - primaryRecommendations.size()
            );
            primaryRecommendations.addAll(supplementary);
        }

        // 转换为VO并计算推荐分数
        List<NovelVo> result = primaryRecommendations.stream()
                .map(novel -> convertToVo(novel, preferredType, viewHistory))
                .collect(Collectors.toList());

        // 按推荐分数降序排序
        result.sort(Comparator.comparingDouble(NovelVo::getRecommendScore).reversed());

        // 执行分页（处理可能的越界情况）
        int end = Math.min(start + size, result.size());
        return result.subList(start, end);
    }

    /**
     * 从浏览历史中提取相关类型（排除用户偏好类型）
     */
    private List<String> extractRelatedTypesFromHistory(@NotNull List<Long> viewHistory, String excludeType) {
        if (viewHistory.isEmpty()) return Collections.emptyList();

        // 查询浏览历史中的小说类型
        List<Novel> historyNovels = novelMapper.findAllById(viewHistory);

        // 统计类型频率（排除偏好类型）
        Map<String, Integer> typeCount = new HashMap<>();
        for (Novel novel : historyNovels) {
            String type = novel.getName();
            if (!type.equals(excludeType)) {
                typeCount.put(type, typeCount.getOrDefault(type, 0) + 1);
            }
        }

        // 取前3个高频类型
        return typeCount.entrySet().stream()
                .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
                .limit(3)
                .map(Map.Entry::getKey)
                .collect(Collectors.toList());
    }

    /**
     * 转换为VO并计算推荐分数
     */
    @NotNull
    private NovelVo convertToVo(Novel novel, String preferredType, List<Long> viewHistory) {
        NovelVo vo = new NovelVo();
        BeanUtils.copyProperties(novel, vo);
        vo.setStatus(NovelStatus.getStatusName(novel.getStatus()));

        // 计算推荐分数（类型匹配度 + 热门程度）
        double typeScore = novel.getName().equals(preferredType) ? 1.0 : 0.6; // 直接匹配1.0，相关类型0.6
        double popularityScore = calculatePopularityScore(novel); // 基于阅读量和评分

        // 综合得分
        vo.setRecommendScore(typeScore * 0.7 + popularityScore * 0.3 );
        return vo;
    }

    /**
     * 计算热门程度分数（0-1）
     */
    private double calculatePopularityScore(@NotNull Novel novel) {
        // 归一化处理，避免数值过大
        double viewScore = novel.getViewCount() / 10000.0;   // 假设1万次阅读为满分
        double scoreScore = novel.getScoreCount() / 500.0;   // 假设500评分人数为满分
        return Math.min(viewScore * 0.6 + scoreScore * 0.4, 1.0);
    }



    /**
     * 获取用户id
     * @return
     */
    @Nullable
    @Contract(pure = true)
    private Long getUserId() {
        LoginUser loginUser = LoginUserHolder.getLoginUser();
        //判断是否登录
        if(loginUser == null)
        {
            throw new BaseException(ResultCodeEnum.TOKEN_INVALID);
        }
        return loginUser.getUserId();
    }

    /**
     * 计算分页
     * @param pageNum
     * @param size
     * @return
     */
    @Nullable
    @Contract(pure = true)
    private Integer getStart(Integer pageNum, Integer size) {
        //判断参数是否为空
        if (pageNum == null || size == null) {
            size = 20;
            pageNum = 1;
        }
        return (pageNum - 1) * size;
    }

    /**
     * 拷贝数据
     * @param novels
     * @return
     */
    @Nullable
    @Contract(pure = true)
    private List<NovelVo> Copy(@NotNull List<Novel> novels) {
        //创建集合
        List<NovelVo> novelVos = new ArrayList<>();
        //循环遍历数据
        for (Novel novel : novels) {
            NovelVo novelVo = new NovelVo();
            //进行数据拷贝
            BeanUtils.copyProperties(novel,novelVo);
            novelVo.setStatus(NovelStatus.getStatusName(novel.getStatus()));
            novelVos.add(novelVo);
        }
        return novelVos;
    }
}
